/*
 *  Copyright 2016 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

package org.webrtc;

import android.content.Context;
import android.os.SystemClock;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

public class FileVideoCapturer implements VideoCapturer {
    private interface VideoReader {
        VideoFrame getNextFrame();

        void close();
    }

    /**
     * Read video data from file for the .y4m container.
     */
    @SuppressWarnings("StringSplitter")
    private static class VideoReaderY4M implements VideoReader {
        private static final String TAG = "VideoReaderY4M";
        private static final String Y4M_FRAME_DELIMETER = "FRAME";
        private static final int FRAME_DELIMETER_LENGTH = Y4M_FRAME_DELIMETER.length() + 1;

        private final int frameWidth;
        private final int frameHeight;
        // First char after header
        private final long videoStart;
        private final RandomAccessFile mediaFile;
        private final FileChannel mediaFileChannel;

        public VideoReaderY4M(String file) throws IOException {
            mediaFile = new RandomAccessFile(file, "r");
            mediaFileChannel = mediaFile.getChannel();
            StringBuilder builder = new StringBuilder();
            for (; ; ) {
                int c = mediaFile.read();
                if (c == -1) {
                    // End of file reached.
                    throw new RuntimeException("Found end of file before end of header for file: " + file);
                }
                if (c == '\n') {
                    // End of header found.
                    break;
                }
                builder.append((char) c);
            }
            videoStart = mediaFileChannel.position();
            String header = builder.toString();
            String[] headerTokens = header.split("[ ]");
            int w = 0;
            int h = 0;
            String colorSpace = "";
            for (String tok : headerTokens) {
                char c = tok.charAt(0);
                switch (c) {
                    case 'W':
                        w = Integer.parseInt(tok.substring(1));
                        break;
                    case 'H':
                        h = Integer.parseInt(tok.substring(1));
                        break;
                    case 'C':
                        colorSpace = tok.substring(1);
                        break;
                }
            }
            Logging.d(TAG, "Color space: " + colorSpace);
            if (!colorSpace.equals("420") && !colorSpace.equals("420mpeg2")) {
                throw new IllegalArgumentException(
                        "Does not support any other color space than I420 or I420mpeg2");
            }
            if ((w % 2) == 1 || (h % 2) == 1) {
                throw new IllegalArgumentException("Does not support odd width or height");
            }
            frameWidth = w;
            frameHeight = h;
            Logging.d(TAG, "frame dim: (" + w + ", " + h + ")");
        }

        @Override
        public VideoFrame getNextFrame() {
            final long captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime());
            final JavaI420Buffer buffer = JavaI420Buffer.allocate(frameWidth, frameHeight);
            final ByteBuffer dataY = buffer.getDataY();
            final ByteBuffer dataU = buffer.getDataU();
            final ByteBuffer dataV = buffer.getDataV();
            final int chromaHeight = (frameHeight + 1) / 2;
            final int sizeY = frameHeight * buffer.getStrideY();
            final int sizeU = chromaHeight * buffer.getStrideU();
            final int sizeV = chromaHeight * buffer.getStrideV();

            try {
                ByteBuffer frameDelim = ByteBuffer.allocate(FRAME_DELIMETER_LENGTH);
                if (mediaFileChannel.read(frameDelim) < FRAME_DELIMETER_LENGTH) {
                    // We reach end of file, loop
                    mediaFileChannel.position(videoStart);
                    if (mediaFileChannel.read(frameDelim) < FRAME_DELIMETER_LENGTH) {
                        throw new RuntimeException("Error looping video");
                    }
                }
                String frameDelimStr = new String(frameDelim.array(), Charset.forName("US-ASCII"));
                if (!frameDelimStr.equals(Y4M_FRAME_DELIMETER + "\n")) {
                    throw new RuntimeException(
                            "Frames should be delimited by FRAME plus newline, found delimter was: '"
                                    + frameDelimStr + "'");
                }

                mediaFileChannel.read(dataY);
                mediaFileChannel.read(dataU);
                mediaFileChannel.read(dataV);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            return new VideoFrame(buffer, 0 /* rotation */, captureTimeNs);
        }

        @Override
        public void close() {
            try {
                // Closing a file also closes the channel.
                mediaFile.close();
            } catch (IOException e) {
                Logging.e(TAG, "Problem closing file", e);
            }
        }
    }

    private final static String TAG = "FileVideoCapturer";
    private final VideoReader videoReader;
    private CapturerObserver capturerObserver;
    private final Timer timer = new Timer();

    private final TimerTask tickTask = new TimerTask() {
        @Override
        public void run() {
            tick();
        }
    };

    public FileVideoCapturer(String inputFile) throws IOException {
        try {
            videoReader = new VideoReaderY4M(inputFile);
        } catch (IOException e) {
            Logging.d(TAG, "Could not open video file: " + inputFile);
            throw e;
        }
    }

    public void tick() {
        VideoFrame videoFrame = videoReader.getNextFrame();
        capturerObserver.onFrameCaptured(videoFrame);
        videoFrame.release();
    }

    @Override
    public void initialize(SurfaceTextureHelper surfaceTextureHelper, Context applicationContext,
                           CapturerObserver capturerObserver) {
        this.capturerObserver = capturerObserver;
    }

    @Override
    public void startCapture(int width, int height, int framerate) {
        timer.schedule(tickTask, 0, 1000 / framerate);
    }

    @Override
    public void stopCapture() throws InterruptedException {
        timer.cancel();
    }

    @Override
    public void changeCaptureFormat(int width, int height, int framerate) {
        // Empty on purpose
    }

    @Override
    public void dispose() {
        videoReader.close();
    }

    @Override
    public boolean isScreencast() {
        return false;
    }
}
